package devcken.springframework.web.multipart;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;

/**
 * 파일 다운로드를 위한 스트림 뷰를 정의(스프링 추상 뷰 재정의)한다.
 * @author Devcken
 */
public class OutputFileStreamView extends AbstractView
{
	private static final Logger logger = LoggerFactory.getLogger(OutputFileStreamView.class);
	
	/**
	 * 아카이브 아이디에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_ID_PARAMETER_NAME = "archiveId";
	
	/**
	 * 아카이브 파일 크기에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_FILE_SIZE_PARAMETER_NAME = "fileSize";
	
	/**
	 * 아카이브 파일의 컨텐츠 타입에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_CONTENT_TYPE_PARAMETER_NAME = "contentType";
	
	/**
	 * 아카이브 파일의 실제 이름에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_ORIGINAL_NAME_PARAMETER_NAME = "originalName";
	
	/**
	 * 아카이브 파일의 마스크 이름에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_MASKED_NAME_PARAMETER_NAME = "maskedName";
	
	/**
	 * 아카이브 파일의 상대 경로에 대한 파라메터 명칭을 나타낸다.
	 */
	public final static String _ARCHIVE_RELATIVE_PATH_PARAMETER_NAME = "relativePath";
	
	/**
	 * 업로드 루트 파라메터 명칭을 나타낸다.
	 */
	public final static String _UPLOAD_ROOT_PARAMETER_NAME = "uploadRoot";
	
	public OutputFileStreamView()
	{
		// 기본 컨텐츠 타입(MIME)을 설정한다.
		super.setContentType("application/octet-stream");
	}

	/**
	 * 다운로드 요청에 대한 파일을 출력 스트림으로 내보낸다.
	 * @param model 뷰로 전달되는 파라메터 목록으로 아카이브 아이디와 업로드 최상위 경로가 전달된다.
	 * @param request HTTP 요청이 전달된다.
	 * @param response HTTP 응답이 전달된다.
	 */
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)	throws Exception
	{
		long fileSize = 0L;
		
		String contentType = StringUtils.EMPTY;
		
		String originalName = StringUtils.EMPTY;
		
		String maskedName = StringUtils.EMPTY;
		
		String relativePath = StringUtils.EMPTY;
		
		if (model.containsKey(_ARCHIVE_FILE_SIZE_PARAMETER_NAME))
		{
			throw new NullPointerException("");
		}
		
		fileSize = Long.parseLong(model.get(_ARCHIVE_FILE_SIZE_PARAMETER_NAME).toString());
		
		if (!model.containsKey(_ARCHIVE_CONTENT_TYPE_PARAMETER_NAME))
		{
			throw new NullPointerException("");
		}
		
		contentType = model.get(_ARCHIVE_CONTENT_TYPE_PARAMETER_NAME).toString();
		
		if (!model.containsKey(_ARCHIVE_ORIGINAL_NAME_PARAMETER_NAME))
		{
			throw new NullPointerException("");
		}
		
		originalName = model.get(_ARCHIVE_ORIGINAL_NAME_PARAMETER_NAME).toString();
		
		if (!model.containsKey(_ARCHIVE_MASKED_NAME_PARAMETER_NAME))
		{
			throw new NullPointerException("");
		}
		
		maskedName = model.get(_ARCHIVE_MASKED_NAME_PARAMETER_NAME).toString();
		
		if (!model.containsKey(_ARCHIVE_RELATIVE_PATH_PARAMETER_NAME))
		{
			throw new NullPointerException("");
		}
		
		relativePath = model.get(_ARCHIVE_RELATIVE_PATH_PARAMETER_NAME).toString();
		
		// 헤더에 파일의 크기를 설정한다. 파일의 크기가 정수형의 범위를 벗어날 수도 있으므로 문자열로 설정한다.
		response.setHeader("Content-Length", "" + fileSize);
		
		// 컨텐츠 타입(MIME)을 설정한다.
		response.setContentType(contentType);
		
		// 다운로드 시에 다운로드 받는 파일의 명칭을 UTF-8로 변환해준다.
		// 이렇게 하는 이유는 다운로드 받는 대상 어플리케이션이 UTF-8만을 지원하는 경우 다운로드가 거부될 수도 있기 때문이다.
		response.setHeader("Content-Disposition",
				"attachment;fileName=\"" + URLEncoder.encode(originalName.replaceAll(" ", "-"), "UTF-8") + "\";");
		
		// 업로드 최상위 경로를 가져온다.
		String uploadRoot = model.get(_UPLOAD_ROOT_PARAMETER_NAME).toString();
		
		// 파일의 실제 경로(서버 측 로컬 경로)를 가져온다. 업로드된 파일은 마스크된 이름으로 저장된다.
		String filePath = String.format("{}{}{}", uploadRoot, relativePath, maskedName);
		
		OutputStream outputStream = response.getOutputStream();
		FileInputStream fileStream = null;
		
		try
		{
			fileStream = new FileInputStream(filePath);
			
			// HTTP 응답의 출력 스트림에 파일 스트림을 쓴다.
			FileCopyUtils.copy(fileStream, outputStream);
		}
		catch (FileNotFoundException error)
		{
			logger.error("다운로드하려는 파일({})이 존재하지 않습니다.", filePath);
		}
		catch (SocketException error)
		{
			// 소켓 예외인 경우 파일 다운로드가 중지됐을 때 발생한다.
			if (logger.isDebugEnabled())
			{
				logger.debug("파일 스트림 복사가 중지되었습니다.");
			}
		}
		catch (IOException error)
		{
			logger.error("파일 스트림 복사에 실패했습니다.", error);
		}
		finally
		{
			if (fileStream != null)
			{
				fileStream.close();
			}
			
			if (outputStream != null)
			{
				outputStream.flush();
				outputStream.close();
			}
		}
	}
}